<?php
/**
 * 观察者模式
 * PHP中提供了 观察者(observer) 和 被观察者(subject) 的接口 SplSubject (PHP 5 >= 5.1.0)
 * SplSubject  {
 *     abstract public void attach ( SplObserver $observer )
 *     abstract public void detach ( SplObserver $observer )
 *     abstract public void notify ( void )
 * }
 */

/**
 * 根据 登录次数 和 推送URL 进行观察,
 * 定义user类，实现SplSubject接口
 */
class User implements SplSubject {
    public $login_count; //登录次数
    public $push_url; //推送的URL
    protected $observers; //储存被观察的对象

    public function __construct($login_count, $push_url) {
        $this->login_count = $login_count;
        $this->push_url = $push_url;
        $this->observers = new SplObjectStorage();
    }

    /**
     * 登录操作
     */
    public function login() {
        //todo 登录的具体逻辑实现...

        //登录后，触发通知
        $this->notify();
    }

    /**
     * 观察
     */
    public function attach(SPLObserver $observer) {
        $this->observers->attach($observer);
    }

    /**
     * 取消观察
     */
    public function detach(SPLObserver $observer) {
        $this->observers->detach($observer);
    }

    /**
     * 通知
     */
    public function notify() {
        $this->observers->rewind();
        //循环获取储存的对象
        while ($this->observers->valid()) {
            $observer = $this->observers->current();
            $observer->update($this);
            $this->observers->next();
        }
    }
}


/**
 * 假设现在有个 "安全模块" 要来观察, 设定登录次数小于3属于正常操作,否则属于异常操作
 */
class Security implements SPLObserver {
    public function update(SplSubject $subject) {
        if ($subject->login_count < 3) {
            echo '当前第' . $subject->login_count . '次登录,正常操作.';
        } else {
            echo '当前第' . $subject->login_count . '次登录,出现异常!';
        }
        echo PHP_EOL;
    }
}

/**
 * 假设现在又有个 "推送模块" 要来观察,登录成功后给xxx接口推送一条记录
 */
class Push implements SPLObserver {
    public function update(SplSubject $subject) {
        echo "给 {$subject->push_url} 推送登录数据";
        echo PHP_EOL;
    }
}

//实施观察, 实例化User
$user = new User(1, 'www.xxx.com');
//安全模块 的观察
$user->attach(new Security());
//推送模块 的观察
$user->attach(new Push());
//执行登录操作
$user->login();

/*
当前第1次登录,正常操作.
给 www.xxx.com 推送登录数据
*/